#include <iostream>
#include <fcntl.h>
#include <unistd.h>
#include <fstream>
#include <filesystem>
#include <stdlib.h>
#include "ipcamera.h"
#include "tool.hpp"

extern "C" {
    #include "libavformat/avformat.h"
    #include "libavcodec/avcodec.h"
    #include "libswscale/swscale.h"
    #include "libavutil/imgutils.h"
    #include "libavutil/log.h"
    #include "turbojpeg.h"
}

using namespace std;

struct FFmpegDeposit {
    FFmpegDeposit() = default;
    ~FFmpegDeposit() {
        if (format_context != nullptr) {
            avformat_close_input(&format_context);
        }
        if (dictionary != nullptr) {
            av_dict_free(&dictionary);
        }
        if (codec_context != nullptr) {
            avcodec_free_context(&codec_context);
        }
    }

    AVFormatContext*    format_context {nullptr};
    AVDictionary*       dictionary {nullptr};
    AVCodecContext*     codec_context {nullptr};
};

int main(int argc, char *argv[]) {
    if (argc <= 2) {
        return -1;
    }
    string camera_ip = argv[1];
    string onvif_user = "admin";
    string onvif_passwd = argv[2];

    // 发送探针、获取设备能力
    auto ip_camera = onvif::ipcamera(camera_ip, onvif_user, onvif_passwd, 0);
    if (auto status = ip_camera.CheckDevice(); !status) {
        cerr << "camera onvif check failed. error info: " << ip_camera.GetErrorInfo() << endl;
        exit(-1);
    }

    // 获取IPCamera主码流RTSP地址
    string camera_rtsp_url {};
    if (camera_rtsp_url = ip_camera.GetRTSPString(); camera_rtsp_url.empty()) {
        cerr << "get rtsp camera url failed. error info: " << ip_camera.GetErrorInfo() << endl;
        exit(-1);
    }
    cout << "Main RTSP: " << camera_rtsp_url << endl;

    // 获取IPCamera主码流格式信息
    onvif::VideoFormat video_format {};
    if (auto status = ip_camera.GetVideoFormat(video_format); !status) {
        cerr << "get video format failed. error info: " << ip_camera.GetErrorInfo() << endl;
        exit(-1);
    }
    cout << "Video Format: " << endl;
    cout << "  Encode name: " << video_format.encodec_str << endl;
    cout << "  Frame rate: " << video_format.frame_rate << endl;
    cout << "  Bit rate: " << video_format.bit_rate << endl;
    cout << "  Key frame interval: " << video_format.keyframe_interval << endl;

    // 获取IPCamera时间
    string camera_datetime_str {};
    if (camera_datetime_str = ip_camera.GetDeviceDatetime(); camera_datetime_str.empty()) {
        cerr << "get camera datetime failed. error info: " << ip_camera.GetErrorInfo() << endl;
        exit(-1);
    }
    auto local_datetime_str = Tool::local_time_str();
    cout << "camera datetime: " << camera_datetime_str << " (local: " << local_datetime_str << ")" << endl;

// 接收视频流部分
    FFmpegDeposit deposit {};

    av_dict_set(&deposit.dictionary, "rtbufsize", "20971520", 0);
    av_dict_set(&deposit.dictionary, "rtsp_transport", "tcp", 0);

    av_log_set_level(AV_LOG_ERROR);
    deposit.format_context = avformat_alloc_context();

    // 打开RTSP流
    int open_status = avformat_open_input(&deposit.format_context, camera_rtsp_url.data(), nullptr, &deposit.dictionary);
    if (open_status != 0) {
        cerr << "rtsp stream open failed. " << camera_rtsp_url << endl;
        return -1;
    }
    cout << endl;
    cout << "rtsp open success" << endl;

    // 查找是否存在流信息
    if (int status = avformat_find_stream_info(deposit.format_context, nullptr); status != 0) {
        cerr << "find stream info failed." << endl;
        return -1;
    }

    // 查找视频流
    AVStream* video_stream {nullptr};
    for (int i = 0; i < deposit.format_context->nb_streams; ++i) {
        auto *av_stream = deposit.format_context->streams[i];
        if (av_stream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
            video_stream = av_stream;
            break;
        }
    }
    if (video_stream == nullptr) {
        cerr << "not find video stream" << endl;
        return -1;
    }
    if (video_stream->r_frame_rate.num == 0) {
        cerr << "video stram abnormal, frame rate is 0" << endl;
    }

    // 获取解码器参数
    auto* codec_param = video_stream->codecpar;
    printf("Width: %d Height: %d\n; frame rate: %d; bit rate: %d\n", codec_param->width, codec_param->height, 
            video_stream->r_frame_rate.den, codec_param->bit_rate);
    fflush(stdout);

    // 查找解码器信息
    const AVCodec *codec = avcodec_find_decoder(codec_param->codec_id);
    if (codec == nullptr) {
        cerr << "not support codec: %d" << codec_param->codec_id << endl;
        return -1;
    }
    
    // 创建解码器上下文并打开解码器
    deposit.codec_context = avcodec_alloc_context3(codec);
    if (int status = avcodec_parameters_to_context(deposit.codec_context, codec_param); status < 0) {
        cerr << "Fill codec param info in codec context failed." << endl;
        return -1;
    }
    if (int status = avcodec_open2(deposit.codec_context, codec, nullptr); status != 0) {
        cerr << "open decodec failed" << endl;
        return -1;
    }
    
    const char file_path[] {"./pic/"};
    if (!filesystem::exists(file_path)) {
        filesystem::create_directory(file_path);
    }

    auto* frame = av_frame_alloc();
    auto* packet = av_packet_alloc();
    
    string start = ip_camera.GetDeviceDatetime();
    uint64_t start_timestamp {};
    // 接收帧原始数据
    while (av_read_frame(deposit.format_context, packet) == 0) {
        if (packet->stream_index == video_stream->index && packet->pts != AV_NOPTS_VALUE) {
            printf("pts: %d, duration: %d, base time: %d/%d %s", packet->pts, packet->duration, 
                    video_stream->time_base.num, video_stream->time_base.den,
                    packet->flags & AV_PKT_FLAG_KEY ? "I" : "");

            // 原始包解帧数据
            avcodec_send_packet(deposit.codec_context, packet);
            avcodec_receive_frame(deposit.codec_context, frame);

            if (frame->format >= 0 && frame->data[0] != nullptr) {
                // 帧YUV数据转RGB
                auto* sws_context = sws_getContext(frame->width, frame->height, static_cast<AVPixelFormat>(frame->format), 
                                                    frame->width, frame->height, AV_PIX_FMT_RGB24, 0, nullptr, nullptr, nullptr);
                int line_size[8] = {frame->linesize[0] * 3};
                int num_bytes = av_image_get_buffer_size(AV_PIX_FMT_RGB24, frame->width, frame->height, 1);
                
                auto* rgb_buffer = new uint8_t[num_bytes] {};
                uint8_t* rgb_handle_buffer[8] {rgb_buffer};
                sws_scale(sws_context, frame->data, frame->linesize, 0, frame->height, rgb_handle_buffer, line_size);

                tjhandle handle = tjInitCompress();
                uint8_t* jpeg_data {nullptr};
                unsigned long jpeg_size {0};
                int status = tjCompress2(handle, rgb_handle_buffer[0], frame->width, 0, frame->height, TJPF_RGB,
                                            &jpeg_data, &jpeg_size, TJSAMP_420, 100, TJFLAG_FASTDCT | TJFLAG_FORCESSE3);
                tjDestroy(handle);
                if (status != 0) {
                    cerr << "Jpeg file encode failed" << endl;
                } else {
                    string current_time = Tool::local_time_str(Tool::DATETIME_FORMAT::CONTINUE);
                    string jpeg_file_pathname  = file_path + current_time + ".jpg";
                    if (filesystem::exists(jpeg_file_pathname)) {
                        filesystem::remove(jpeg_file_pathname);
                    }

                    ofstream out_file(jpeg_file_pathname, ios_base::out | ios_base::binary);
                    if (!out_file.good()) {
                        cerr << "file create failed. " << jpeg_file_pathname << endl;
                        break;
                    }
                    out_file.write(reinterpret_cast<char*>(jpeg_data), jpeg_size);
                    out_file.close();
                    free(jpeg_data);
                }
            }

            av_frame_unref(frame);
        }

        av_packet_unref(packet);
    }
    return 0;
}